package biz.datalk.industrialland.common.log.aspect;

import biz.datalk.commons.utils.json.JsonUtil;
import biz.datalk.industrialland.common.def.CommonConstant;
import biz.datalk.industrialland.common.exception.ApplicationException;
import biz.datalk.industrialland.common.log.aspect.annotation.SysLog;
import biz.datalk.industrialland.common.log.config.LogProperties;
import biz.datalk.industrialland.common.log.def.LogTypeEnum;
import biz.datalk.industrialland.common.log.def.OptEnum;
import biz.datalk.industrialland.common.log.def.OptResultEnum;
import biz.datalk.industrialland.common.log.pojo.bo.LogBO;
import biz.datalk.industrialland.common.log.service.ISysLogService;
import biz.datalk.industrialland.common.log.util.LogJwtUtil;
import biz.datalk.industrialland.common.log.util.SysLogUtil;
import biz.datalk.industrialland.common.pojo.vo.LoginUser;
import biz.datalk.industrialland.common.result.UnifyResult;
import biz.datalk.industrialland.common.result.UnifyResultUtil;
import biz.datalk.industrialland.common.util.JwtUtil;
import biz.datalk.industrialland.common.util.RedisUtil;
import biz.datalk.industrialland.common.util.SpringContextUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * 系统日志，切面处理类
 *
 * @author tarofang@163.com
 * @date 2020-11-24
 */
@Aspect
@Component
public class SysLogAspect {
    private static final Logger log = LoggerFactory.getLogger(SysLogAspect.class);

    private static final ThreadLocal<Long> threadLocal = new NamedThreadLocal<>("SysLogAspectThreadLocal");

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Autowired
    private ISysLogService sysLogService;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private LogProperties logProperties;


    @Pointcut(value = "@annotation(biz.datalk.industrialland.common.log.aspect.annotation.SysLog)")
    public void logPointCut() {
    }

    // @Around("logPointCut()")
    // public Object around(ProceedingJoinPoint point) throws Throwable {
    //     long beginTime = System.currentTimeMillis();
    //     //执行方法
    //     Object result = point.proceed();
    //     //执行时长(毫秒)
    //     long time = System.currentTimeMillis() - beginTime;
    //
    //     //保存日志
    //     saveSysLog(point, time, result);
    //
    //     return result;
    // }

    /**
     * 前置通知
     * 设置访问进入方法的时间
     */
    @Before("logPointCut()")
    public void before() {
        // log.debug("before =========================================== {}", AopContext.currentProxy().getClass());
        threadLocal.set(System.currentTimeMillis());
    }

    /**
     * 后置通知
     * 在方法正常执行完成进行通知
     */
    @AfterReturning(pointcut = "logPointCut()", returning = "resultObj")
    public void afterReturning(JoinPoint joinPoint, Object resultObj) {
        saveLog(joinPoint, resultObj, null);
    }

    /**
     * 异常通知
     * 在方法出现异常时进行通知
     */
    @AfterThrowing(value = "logPointCut()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
        saveLog(joinPoint, null, ex);
    }

    /**
     * 保存日志
     *
     * @param joinPoint 链接点
     * @param result    接口正常的请求结果
     * @param throwable 接口抛出异常
     *
     * @author tarofang@163.com
     * @date 2022年09月30日
     */
    private void saveLog(JoinPoint joinPoint, Object result, Throwable throwable) {
        try {
            // 计算执行时间
            Long startTimestamp = threadLocal.get();
            startTimestamp = startTimestamp == null ? 0L : startTimestamp;
            long endTimestamp = System.currentTimeMillis();
            long costTime = startTimestamp == 0L ? -1L : (endTimestamp - startTimestamp);

            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            SysLog syslog = method.getAnnotation(SysLog.class);
            LogTypeEnum logType = LogTypeEnum.UNKNOWN;
            OptEnum optEnum = null;
            String logContent = "";
            if (syslog != null) {
                optEnum = syslog.value();
                logType = syslog.logType();
                logContent = syslog.logContent();
            }
            if (optEnum == null) {
                log.warn("@SysLog optEum is null");
                return;
            }

            HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
            // 获取用户信息
            long sysUserId = -1L;
            String username = "";
            try {
                Object obj = SecurityUtils.getSubject().getPrincipal();
                if (obj instanceof LoginUser) {
                    LoginUser loginUser = (LoginUser) obj;
                    sysUserId = loginUser.getId();
                    username = loginUser.getUsername();
                } else {
                    String accessToken = JwtUtil.getRequestToken(request);
                    accessToken = StringUtils.trimToEmpty(accessToken);
                    String jwtToken = redisUtil.getStr(CommonConstant.PREFIX_USER_TOKEN.concat(accessToken));
                    if (StringUtils.isNotBlank(jwtToken)) {
                        Map<String, String> payloads = LogJwtUtil.getPayloads(jwtToken, JwtUtil.PAYLOAD_SYS_USER_ID, JwtUtil.PAYLOAD_USERNAME);
                        sysUserId = MapUtils.getLong(payloads, JwtUtil.PAYLOAD_SYS_USER_ID, -1L);
                        username = MapUtils.getString(payloads, JwtUtil.PAYLOAD_USERNAME, StringUtils.EMPTY);
                    }
                }
            } catch (Exception ex) {
                if (log.isDebugEnabled()) {
                    log.debug("{}", ex.getMessage());
                }
            }

            // 默认操作成功
            OptResultEnum optResult = OptResultEnum.SUCCESS;
            if (result instanceof UnifyResult) {
                UnifyResult unifyResult = (UnifyResult) result;
                optResult = UnifyResultUtil.isSuccess(unifyResult) ? OptResultEnum.SUCCESS : OptResultEnum.FAIL;
            }

            String reqResult = "";
            if (throwable != null) {
                optResult = OptResultEnum.FAIL;
                if (throwable instanceof ApplicationException) {
                    ApplicationException appEx = (ApplicationException) throwable;
                    reqResult = UnifyResult.fail(appEx.getCode(), appEx.getMessage()).asJson();
                } else {
                    reqResult = this.getThrowableStr(throwable);
                }
            } else {
                if (result instanceof ModelAndView) {
                    ModelAndView mav = (ModelAndView) result;
                    String tmpStr = StringUtils.trimToEmpty(mav.toString());
                    int maxLen = 2000;
                    reqResult = tmpStr.length() > maxLen ? tmpStr.substring(0, maxLen) : tmpStr;
                } else {
                    reqResult = JsonUtil.getJsonFromObject(result, Boolean.TRUE);
                }
            }

            LogBO logBO = SysLogUtil.pkgLogBo(request, sysUserId, username, logType, optEnum, logContent, optResult, reqResult, "", costTime, this.logProperties.getLogServerIp());

            sysLogService.saveLogBo(logBO);
        } catch (Exception ex) {
            log.warn("{}", ex.getMessage());
        }
    }

    private String getThrowableStr(Throwable throwable) {
        String rootExceptionName = throwable.getClass().getName();
        Throwable rootCause = ExceptionUtils.getRootCause(throwable);
        rootCause = rootCause == null ? throwable : rootCause;
        StackTraceElement[] stackTraceElements = rootCause.getStackTrace();
        StringBuilder resultContent = new StringBuilder("Exception：" + rootExceptionName);
        int count = 0;
        int maxTrace = 3;
        for (StackTraceElement stackTraceElement : stackTraceElements) {
            // System.out.println("stackTraceElement.getFileName() = " + stackTraceElement.getFileName());
            if (count < maxTrace) {
                int lineNumber = stackTraceElement.getLineNumber();
                if (lineNumber == -1) {
                    continue;
                }
                resultContent.append("\n") //
                        .append(stackTraceElement.getClassName()) //
                        .append(".").append(stackTraceElement.getMethodName()) //
                        .append("(").append(stackTraceElement.getFileName()).append(":").append(lineNumber).append(")");
                count++;
                if (count == maxTrace) {
                    break;
                }
            }
        }
        return resultContent.toString();
    }

    @Deprecated
    private void saveSysLog(ProceedingJoinPoint point, long time, Object result) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        SysLog syslog = method.getAnnotation(SysLog.class);
        LogTypeEnum logType = LogTypeEnum.UNKNOWN;
        OptEnum optEnum = null;
        String logContent = "";
        if (syslog != null) {
            optEnum = syslog.value();
            logType = syslog.logType();
            logContent = syslog.logContent();
        }

        if (optEnum == null) {
            log.warn("@SysLog optEum is null");
            return;
        }

        HttpServletRequest request = SpringContextUtils.getHttpServletRequest();

        // 获取用户信息
        long sysUserId = -1L;
        String username = "";
        try {
            // if (useJwtToken) {
            String imToken = LogJwtUtil.getRequestToken(request);
            imToken = StringUtils.trimToEmpty(imToken);
            String jwtToken = redisUtil.getStr(CommonConstant.PREFIX_USER_TOKEN.concat(imToken));
            jwtToken = StringUtils.trimToEmpty(jwtToken);
            if (StringUtils.isNotBlank(jwtToken)) {
                Map<String, String> payloads = LogJwtUtil.getPayloads(jwtToken, "SYS_USER_ID_LONG", "SYS_USERNAME");
                sysUserId = MapUtils.getLong(payloads, "SYS_USER_ID_LONG", -1L);
                username = MapUtils.getString(payloads, "SYS_USERNAME", "");
            }
            // } else {
            //     Session session = SecurityUtils.getSubject().getSession();
            //     Object sysUserIdLong = session.getAttribute("SYS_USER_ID_LONG");
            //     if (sysUserIdLong instanceof Long) {
            //         sysUserId = (Long) sysUserIdLong;
            //     }
            //     Object usernameString = session.getAttribute("SYS_USERNAME");
            //     if (usernameString instanceof String) {
            //         username = StringUtils.trimToEmpty((String) usernameString);
            //     }
            // }
        } catch (Exception ex) {
            if (log.isDebugEnabled()) {
                log.debug("{}", ex.getMessage());
            }
        }


        // 默认操作成功
        OptResultEnum optResult = OptResultEnum.SUCCESS;
        if (result instanceof UnifyResult) {
            UnifyResult unifyResult = (UnifyResult) result;
            optResult = UnifyResultUtil.isSuccess(unifyResult) ? OptResultEnum.SUCCESS : OptResultEnum.FAIL;
        }

        // 获取请求/响应相关数据
        // String reqResult = (result instanceof ModelAndView)  //
        //         ? "ModelAndView" //
        //         : JsonUtil.getJsonFromObject(result, Boolean.TRUE); //
        String reqResult = "";
        if (result instanceof ModelAndView) {
            ModelAndView mav = (ModelAndView) result;
            String tmpStr = StringUtils.trimToEmpty(mav.toString());
            int maxLen = 2000;
            reqResult = tmpStr.length() > maxLen ? tmpStr.substring(0, maxLen) : tmpStr;
        } else {
            reqResult = JsonUtil.getJsonFromObject(result, Boolean.TRUE);
        }


        LogBO logBO = SysLogUtil.pkgLogBo(request, sysUserId, username, logType, optEnum, logContent, optResult, reqResult, "", time, this.logProperties.getLogServerIp());

        sysLogService.saveLogBo(logBO);
    }


}
